Current File : /home/jeconsul/public_html/wp-content/plugins/suremails/src/screens/logs/drawer-log-body.js
import { useState, memo, useEffect, useMemo } from '@wordpress/element';
import { Badge, Select } from '@bsf/force-ui';
import { __, sprintf } from '@wordpress/i18n';
import {
	formatDate,
	parseHeaders,
	getStatusLabel,
	getStatusVariant,
	ShadowDOM,
	stringToHtml,
	containsHtmlTag,
	convertUTCConnection,
	get_connection_message,
} from '@utils/utils';
import CollapsibleSection from '@components/collapsible-section';
import ContentGuardChecks from './content-guard-checks';
import { AttachmentList } from '@components/attachments/attachments';
import Title from '@components/title/title';

const DrawerLogBody = ( { log } ) => {
	const [ selectedRetry, setSelectedRetry ] = useState( null );
	const [ groupedResponses, setGroupedResponses ] = useState( {} );

	const attachments = Array.isArray( log.attachments ) ? log.attachments : [];

	// Parse serialized JSON or comma-separated values in email fields
	const parseEmailField = ( field ) => {
		if ( ! field ) {
			return [];
		}
		try {
			// Check if serialized JSON and parse
			return typeof field === 'string' && field.startsWith( '[' )
				? JSON.parse( field )
				: field.split( ',' ).map( ( email ) => email.trim() );
		} catch {
			return field.split( ',' ).map( ( email ) => email.trim() );
		}
	};

	// Function to extract charset from Content-Type
	const extractCharset = ( contentType ) => {
		if ( ! contentType ) {
			return '';
		}
		const match = contentType.match( /charset=([\w-]+)/i );
		return match ? match[ 1 ] : '';
	};

	// Use the utility function to parse headers
	const headers = log.headers ? parseHeaders( log.headers ) : {};

	// Initialize headerFields with default empty arrays or values
	const headerFields = {
		From: headers.From ? headers.From[ 0 ] : '',
		'Reply-To': headers[ 'Reply-To' ] || [],
		CC: headers.Cc || [],
		BCC: headers.Bcc || [],
		'Content-Type': headers[ 'Content-Type' ]
			? headers[ 'Content-Type' ][ 0 ]
			: '',
		Charset: extractCharset(
			headers[ 'Content-Type' ] ? headers[ 'Content-Type' ][ 0 ] : ''
		),
		'X-Mailer': headers[ 'X-Mailer' ] ? headers[ 'X-Mailer' ][ 0 ] : '',
	};

	useEffect( () => {
		const response = log.response || [];
		const grouped = response.reduce( ( acc, res ) => {
			const retryNumber = Number( res.retry );
			if ( ! acc[ retryNumber ] ) {
				acc[ retryNumber ] = [];
			}
			acc[ retryNumber ].push( res );
			return acc;
		}, {} );

		setGroupedResponses( grouped );
	}, [ log.response ] );

	const sortedRetryKeys = useMemo( () => {
		return Object.keys( groupedResponses )
			.map( Number )
			.sort( ( a, b ) => a - b );
	}, [ groupedResponses ] );

	const retries = useMemo( () => {
		return sortedRetryKeys.map( ( retry ) => ( {
			label: `Response ${ retry + 1 }`,
			value: retry,
		} ) );
	}, [ sortedRetryKeys ] );

	const getRetryLabel = ( value ) => {
		const retry = retries.find( ( r ) => r.value === value );
		return retry ? retry.label : '';
	};

	useEffect( () => {
		if ( retries.length > 0 ) {
			const isValidRetry = retries.some(
				( retry ) => retry.value === selectedRetry
			);
			if ( ! isValidRetry ) {
				setSelectedRetry( retries[ retries.length - 1 ].value );
			}
		} else {
			setSelectedRetry( null );
		}
	}, [ retries, selectedRetry ] );

	const handleRetryChange = ( selectedValue ) => {
		setSelectedRetry( selectedValue );
	};

	const createAndAttachEmailBody = ( content ) => ( node ) => {
		if ( ! node || ! content ) {
			return;
		}
		const emailBodyShadow = new ShadowDOM( node );
		if ( emailBodyShadow.hasChildNodes() ) {
			return;
		}
		// Wrapper for adding 8px padding to the email body
		const wrapper = document.createElement( 'div' );
		wrapper.style.padding = containsHtmlTag( content ) ? '0' : '0.5rem';
		// Escape HTML tags and convert plain text to HTML
		wrapper.innerHTML = stringToHtml( content );
		// Append the wrapper to the email body shadow
		emailBodyShadow.appendChild( wrapper );
	};

	return (
		<div className="rounded-lg bg-background-secondary">
			{ /* Email Information Section */ }
			<CollapsibleSection alwaysOpen>
				<CollapsibleSection.Content>
					<div className="space-y-2">
						<div className="flex items-center justify-between">
							<p>
								<strong className="text-sm font-normal text-text-tertiary">
									{ __( 'Sent by:', 'suremails' ) }
								</strong>{ ' ' }
								<strong className="text-sm font-normal text-text-primary">
									{ log.email_from }
								</strong>
							</p>
							<div className="flex items-center space-x-2">
								<Badge
									className="py-0.5"
									label={ log.connection }
									variant="blue"
									disableHover
								/>
								<Badge
									className="py-0.5"
									label={ getStatusLabel(
										log.status,
										log?.response
									) }
									variant={ getStatusVariant(
										log.status,
										log?.response
									) }
									disableHover
								/>
							</div>
						</div>

						{ /* Second Row: Sent to and Date */ }
						<div className="flex items-center justify-between">
							<p>
								<strong className="text-sm font-normal text-text-tertiary">
									{ __( 'Sent to:', 'suremails' ) }
								</strong>{ ' ' }
								<strong className="text-sm font-normal text-text-primary">
									{ parseEmailField( log.email_to ).join(
										', '
									) }
								</strong>
							</p>
							<p className="text-sm font-normal text-text-secondary">
								{ formatDate( log.updated_at, {
									day: true,
									month: true,
									year: true,
									hour: true,
									minute: true,
									hour12: true,
								} ) }
							</p>
						</div>

						{ /* Third Row: Subject */ }
						<p>
							<strong className="text-sm font-normal text-text-tertiary">
								{ __( 'Subject:', 'suremails' ) }
							</strong>{ ' ' }
							<strong className="text-sm font-normal text-text-primary">
								{ log.subject }
							</strong>
						</p>
						<div className="flex items-start justify-start gap-1">
							<p>
								<strong className="text-sm font-normal text-text-tertiary">
									{ __( 'Resent:', 'suremails' ) }
								</strong>{ ' ' }
								<strong className="text-sm font-normal text-text-primary">
									{ log.meta?.resend }
								</strong>
							</p>
							<p>
								<strong className="text-sm font-normal text-text-tertiary">
									{ __( 'Retries:', 'suremails' ) }
								</strong>{ ' ' }
								<strong className="text-sm font-normal text-text-primary">
									{ log.meta?.retry }
								</strong>
							</p>
						</div>
					</div>
				</CollapsibleSection.Content>
			</CollapsibleSection>

			{ /* Email Body Section */ }
			<CollapsibleSection defaultOpen>
				<CollapsibleSection.Trigger>
					<Title tag="h4" title={ __( 'Email Body', 'suremails' ) } />
				</CollapsibleSection.Trigger>
				<CollapsibleSection.Content className="bg-background-secondary rounded overflow-hidden">
					<div ref={ createAndAttachEmailBody( log.body ) }>
						{ log.body
							? false
							: __( 'No email body available.', 'suremails' ) }
					</div>
				</CollapsibleSection.Content>
			</CollapsibleSection>

			{ /* Content guard checks */ }
			<ContentGuardChecks log={ log } />

			{ /* Server Response Section */ }
			<CollapsibleSection defaultOpen>
				<CollapsibleSection.Trigger>
					<Title
						tag="h4"
						title={ __( 'Server Response', 'suremails' ) }
					/>
				</CollapsibleSection.Trigger>
				<CollapsibleSection.Content>
					<div className="flex flex-col gap-2 mt-2">
						{ ' ' }
						<Select
							value={ getRetryLabel( selectedRetry ) }
							onChange={ ( value ) => handleRetryChange( value ) }
							className="w-full"
							by={ selectedRetry }
						>
							<Select.Button />
							<Select.Options>
								{ retries.map( ( retry ) => (
									<Select.Option
										key={ retry.value }
										value={ retry.value }
									>
										{ retry.label }
									</Select.Option>
								) ) }
							</Select.Options>
						</Select>
						<div className="flex items-start justify-between gap-2 mt-2 rounded-md bg-background-primary">
							<div className="flex-1">
								{ groupedResponses[ selectedRetry ]?.length >
								0 ? (
									<div className="flex flex-col gap-2">
										{ ' ' }
										{ groupedResponses[ selectedRetry ].map(
											( res, index ) => (
												<div
													key={ index }
													className="py-2 px-2 gap-3 rounded border bg-background-secondary"
												>
													{ /* Message and Connection inline */ }
													<div className="flex flex-col gap-1">
														{ ' ' }
														<p className="font-medium text-sm text-text-primary">
															{ __(
																'Message:',
																'suremails'
															) }{ ' ' }
															<span className="font-normal text-sm text-text-secondary">
																{ res.Message }
															</span>
														</p>
														<p className="font-medium text-sm text-text-primary">
															{ __(
																'Connection:',
																'suremails'
															) }{ ' ' }
															<span className="font-normal text-text-secondary">
																{ res.timestamp
																	? get_connection_message(
																			res.Connection,
																			res.timestamp
																	  )
																	: convertUTCConnection(
																			res.Connection
																	  ) }
															</span>
														</p>
													</div>
												</div>
											)
										) }
									</div>
								) : (
									<p className="text-sm text-text-secondary">
										{ __(
											'No server response available.',
											'suremails'
										) }
									</p>
								) }
							</div>
						</div>
					</div>
				</CollapsibleSection.Content>
			</CollapsibleSection>

			{ /* Email Headers Section */ }
			<CollapsibleSection defaultOpen>
				<CollapsibleSection.Trigger>
					<Title
						tag="h4"
						title={ __( 'Email Headers', 'suremails' ) }
					/>
				</CollapsibleSection.Trigger>
				<CollapsibleSection.Content>
					<div className="mt-2 space-y-2">
						<div>
							<strong className="text-sm font-normal text-text-tertiary">
								{ __( 'From:', 'suremails' ) }
							</strong>{ ' ' }
							<strong className="text-sm font-normal text-text-primary">
								{ headerFields.From &&
								headerFields.From.length > 0 ? (
									<span>{ headerFields.From }</span>
								) : (
									''
								) }
							</strong>
						</div>
						<div>
							<strong className="text-sm font-normal text-text-tertiary">
								{ __( 'Reply-To:', 'suremails' ) }
							</strong>{ ' ' }
							<strong className="text-sm font-normal text-text-primary">
								{ headerFields[ 'Reply-To' ] &&
								headerFields[ 'Reply-To' ].length > 0 ? (
									<span>
										{ headerFields[ 'Reply-To' ].join(
											', '
										) }
									</span>
								) : (
									''
								) }
							</strong>
						</div>
						<div>
							<strong className="text-sm font-normal text-text-tertiary">
								{ __( 'CC:', 'suremails' ) }
							</strong>{ ' ' }
							<strong className="text-sm font-normal text-text-primary">
								{ headerFields.CC &&
								headerFields.CC.length > 0 ? (
									<span>
										{ headerFields.CC.join( ', ' ) }
									</span>
								) : (
									''
								) }
							</strong>
						</div>
						<div>
							<strong className="text-sm font-normal text-text-tertiary">
								{ __( 'BCC:', 'suremails' ) }
							</strong>{ ' ' }
							<strong className="text-sm font-normal text-text-primary">
								{ headerFields.BCC &&
								headerFields.BCC.length > 0 ? (
									<span>
										{ headerFields.BCC.join( ', ' ) }
									</span>
								) : (
									''
								) }
							</strong>
						</div>
						<div>
							<strong className="text-sm font-normal text-text-tertiary">
								{ __( 'Content-Type:', 'suremails' ) }
							</strong>{ ' ' }
							<strong className="text-sm font-normal text-text-primary">
								{ headerFields[ 'Content-Type' ] || '' }
							</strong>
						</div>
						<div>
							<strong className="text-sm font-normal text-text-tertiary">
								{ __( 'X-Mailer:', 'suremails' ) }
							</strong>{ ' ' }
							<strong className="text-sm font-normal text-text-primary">
								{ headerFields[ 'X-Mailer' ] || '' }
							</strong>
						</div>
					</div>
				</CollapsibleSection.Content>
			</CollapsibleSection>

			{ /* Email Attachments Section */ }
			<CollapsibleSection defaultOpen={ attachments.length > 0 }>
				<CollapsibleSection.Trigger>
					<Title
						tag="h4"
						title={ sprintf(
							/* translators: %d: Number of attachments. */
							__( 'Attachments (%d)', 'suremails' ),
							attachments.length
						) }
					/>
				</CollapsibleSection.Trigger>
				<CollapsibleSection.Content>
					<AttachmentList attachments={ attachments } />
				</CollapsibleSection.Content>
			</CollapsibleSection>
		</div>
	);
};

export default memo( DrawerLogBody );